เชี่ยวชาญการใช้ React createRef เพื่อจัดการ DOM และ instance ของ component แบบ imperative เรียนรู้วิธีและเวลาที่ควรใช้ใน class component อย่างมีประสิทธิภาพ สำหรับการโฟกัส มีเดีย และการผสานกับไลบรารีภายนอก
React createRef: คู่มือฉบับสมบูรณ์สู่การโต้ตอบโดยตรงกับ Component และ DOM Element
ในภูมิทัศน์ของการพัฒนาเว็บสมัยใหม่ที่กว้างขวางและมักจะซับซ้อน React ได้กลายเป็นกำลังสำคัญ โดยได้รับการยอมรับเป็นหลักในด้านแนวทางแบบ declarative ในการสร้างส่วนติดต่อผู้ใช้ (user interfaces) กระบวนทัศน์นี้สนับสนุนให้นักพัฒนาอธิบายว่า UI ของพวกเขาควรมีลักษณะอย่างไร โดยอิงจากข้อมูล แทนที่จะกำหนดว่า จะบรรลุสถานะทางภาพนั้นได้อย่างไร ผ่านการจัดการ DOM โดยตรง การสรุปนี้ได้ทำให้การพัฒนา UI ง่ายขึ้นอย่างมาก ทำให้แอปพลิเคชันคาดเดาได้ง่ายขึ้น เข้าใจได้ง่ายขึ้น และมีประสิทธิภาพสูง
อย่างไรก็ตาม โลกแห่งความเป็นจริงของเว็บแอปพลิเคชันนั้นไม่ค่อยจะเป็นแบบ declarative ทั้งหมด มีสถานการณ์เฉพาะแต่พบบ่อย ที่การโต้ตอบโดยตรงกับ DOM (Document Object Model) element พื้นฐาน หรือ instance ของ class component กลายเป็นสิ่งที่ไม่ใช่แค่สะดวก แต่จำเป็นอย่างยิ่ง "ช่องทางหลีกหนี" เหล่านี้จากกระบวนการแบบ declarative ของ React เป็นที่รู้จักกันในชื่อ refs ในบรรดากลไกต่างๆ ที่ React มีให้สำหรับการสร้างและจัดการการอ้างอิงเหล่านี้ React.createRef() ถือเป็น API พื้นฐานที่สำคัญ โดยเฉพาะอย่างยิ่งสำหรับนักพัฒนาที่ทำงานกับ class components
คู่มือฉบับสมบูรณ์นี้มีจุดมุ่งหมายเพื่อเป็นแหล่งข้อมูลที่ชัดเจนสำหรับคุณในการทำความเข้าใจ นำไปใช้ และเชี่ยวชาญ React.createRef() เราจะเริ่มต้นการสำรวจอย่างละเอียดเกี่ยวกับวัตถุประสงค์ของมัน เจาะลึกถึง синтаксис และการใช้งานจริง ชี้ให้เห็นถึงแนวทางปฏิบัติที่ดีที่สุด และแยกความแตกต่างจากกลยุทธ์การจัดการ ref อื่นๆ ไม่ว่าคุณจะเป็นนักพัฒนา React ที่มีประสบการณ์และต้องการเสริมความเข้าใจเกี่ยวกับการโต้ตอบแบบ imperative หรือเป็นผู้มาใหม่ที่ต้องการเข้าใจแนวคิดที่สำคัญนี้ บทความนี้จะมอบความรู้ให้คุณเพื่อสร้างแอปพลิเคชัน React ที่แข็งแกร่ง มีประสิทธิภาพ และเข้าถึงได้ทั่วโลก ซึ่งสามารถรับมือกับความต้องการที่ซับซ้อนของประสบการณ์ผู้ใช้สมัยใหม่ได้อย่างงดงาม
การทำความเข้าใจ Refs ใน React: การเชื่อมโยงระหว่างโลก Declarative และ Imperative
โดยแก่นแท้แล้ว React สนับสนุนสไตล์การเขียนโปรแกรมแบบ declarative คุณกำหนด components, state ของมัน, และวิธีการ render จากนั้น React จะเข้ามาจัดการ อัปเดต DOM ของเบราว์เซอร์จริงอย่างมีประสิทธิภาพเพื่อสะท้อน UI ที่คุณประกาศไว้ ชั้นของการสรุปนี้มีพลังมหาศาล ช่วยปกป้องนักพัฒนาจากความซับซ้อนและข้อผิดพลาดด้านประสิทธิภาพของการจัดการ DOM โดยตรง นี่คือเหตุผลที่แอปพลิเคชัน React มักจะให้ความรู้สึกราบรื่นและตอบสนองได้ดี
การไหลของข้อมูลทิศทางเดียวและข้อจำกัดของมัน
จุดแข็งทางสถาปัตยกรรมของ React อยู่ที่การไหลของข้อมูลทิศทางเดียว (unidirectional data flow) ข้อมูลจะไหลลงจาก parent components ไปยัง children ผ่าน props อย่างคาดเดาได้ และการเปลี่ยนแปลง state ภายใน component จะกระตุ้นให้เกิดการ re-render ซึ่งแพร่กระจายผ่าน subtree ของมัน โมเดลนี้ส่งเสริมความสามารถในการคาดการณ์และทำให้การดีบักง่ายขึ้นอย่างมาก เนื่องจากคุณจะรู้เสมอว่าข้อมูลมาจากไหนและมีอิทธิพลต่อ UI อย่างไร อย่างไรก็ตาม ไม่ใช่ทุกการโต้ตอบจะสอดคล้องกับการไหลของข้อมูลจากบนลงล่างนี้อย่างสมบูรณ์แบบ
พิจารณาสถานการณ์เช่น:
- การโฟกัสช่อง input โดยโปรแกรมเมื่อผู้ใช้ไปยังฟอร์ม
- การเรียกใช้เมธอด
play()หรือpause()บน element<video> - การวัดขนาดพิกเซลที่แน่นอนของ
<div>ที่ render แล้ว เพื่อปรับเค้าโครงแบบไดนามิก - การผสานรวมกับไลบรารี JavaScript ของบุคคลที่สามที่ซับซ้อน (เช่น ไลบรารีการสร้างแผนภูมิอย่าง D3.js หรือเครื่องมือสร้างภาพแผนที่) ที่คาดหวังการเข้าถึง DOM container โดยตรง
การกระทำเหล่านี้โดยเนื้อแท้แล้วเป็นแบบ imperative – ซึ่งเกี่ยวข้องกับการสั่งการ element โดยตรงให้ทำบางสิ่งบางอย่าง แทนที่จะเป็นเพียงการประกาศสถานะที่ต้องการ ในขณะที่โมเดล declarative ของ React มักจะสามารถสรุปรายละเอียดแบบ imperative จำนวนมากออกไปได้ แต่มันก็ไม่ได้กำจัดความจำเป็นของมันทั้งหมด นี่คือจุดที่ refs เข้ามามีบทบาท โดยให้ช่องทางหลีกหนีที่ควบคุมได้เพื่อดำเนินการโต้ตอบโดยตรงเหล่านี้
เมื่อใดควรใช้ Refs: การนำทางระหว่างการโต้ตอบแบบ Imperative และ Declarative
หลักการที่สำคัญที่สุดประการเดียวเมื่อทำงานกับ refs คือ ใช้มันเท่าที่จำเป็นและเฉพาะเมื่อจำเป็นจริงๆ เท่านั้น หากงานสามารถทำได้โดยใช้กลไก declarative มาตรฐานของ React (state และ props) นั่นควรเป็นแนวทางที่คุณเลือกเสมอ การพึ่งพา refs มากเกินไปอาจนำไปสู่โค้ดที่เข้าใจ บำรุงรักษา และดีบักได้ยากขึ้น ซึ่งบั่นทอนประโยชน์ที่ React มอบให้
อย่างไรก็ตาม สำหรับสถานการณ์ที่ต้องการการเข้าถึง DOM node หรือ instance ของ component โดยตรงอย่างแท้จริง refs คือโซลูชันที่ถูกต้องและตั้งใจไว้ นี่คือรายละเอียดของกรณีการใช้งานที่เหมาะสม:
- การจัดการ Focus, การเลือกข้อความ, และการเล่นสื่อ: นี่คือตัวอย่างคลาสสิกที่คุณต้องโต้ตอบกับ elements แบบ imperative ลองนึกถึงการโฟกัสอัตโนมัติที่แถบค้นหาเมื่อโหลดหน้าเว็บ การเลือกข้อความทั้งหมดในช่อง input หรือการควบคุมการเล่นของเครื่องเล่นเสียงหรือวิดีโอ การกระทำเหล่านี้มักถูกกระตุ้นโดยเหตุการณ์ของผู้ใช้หรือ lifecycle methods ของ component ไม่ใช่แค่โดยการเปลี่ยน props หรือ state
- การกระตุ้นแอนิเมชันแบบ Imperative: ในขณะที่แอนิเมชันจำนวนมากสามารถจัดการแบบ declarative ด้วย CSS transitions/animations หรือไลบรารีแอนิเมชันของ React แอนิเมชันที่ซับซ้อนและมีประสิทธิภาพสูงบางอย่าง โดยเฉพาะอย่างยิ่งที่เกี่ยวข้องกับ HTML Canvas API, WebGL, หรือที่ต้องการการควบคุมคุณสมบัติของ element อย่างละเอียดซึ่งจัดการได้ดีที่สุดนอกวงจรการ render ของ React อาจจำเป็นต้องใช้ refs
- การผสานรวมกับไลบรารี DOM ของบุคคลที่สาม: ไลบรารี JavaScript ที่มีชื่อเสียงมากมาย (เช่น D3.js, Leaflet สำหรับแผนที่, ชุดเครื่องมือ UI รุ่นเก่าต่างๆ) ถูกออกแบบมาเพื่อจัดการ DOM elements ที่เฉพาะเจาะจงโดยตรง Refs เป็นสะพานเชื่อมที่จำเป็น ช่วยให้ React สามารถ render container element แล้วให้สิทธิ์การเข้าถึง container นั้นแก่ไลบรารีของบุคคลที่สามสำหรับตรรกะการ render แบบ imperative ของตนเอง
-
การวัดขนาดหรือตำแหน่งของ Element: เพื่อที่จะนำเค้าโครงขั้นสูง, virtualization, หรือพฤติกรรมการเลื่อนแบบกำหนดเองไปใช้ คุณมักต้องการข้อมูลที่แม่นยำเกี่ยวกับขนาดของ element, ตำแหน่งของมันเทียบกับ viewport, หรือความสูงของการเลื่อน APIs เช่น
getBoundingClientRect()สามารถเข้าถึงได้บน DOM nodes จริงเท่านั้น ทำให้ refs ขาดไม่ได้สำหรับการคำนวณดังกล่าว
ในทางกลับกัน คุณควร หลีกเลี่ยงการใช้ refs สำหรับงานที่สามารถทำได้แบบ declarative ซึ่งรวมถึง:
- การแก้ไขสไตล์ของ component (ใช้ state สำหรับการกำหนดสไตล์ตามเงื่อนไข)
- การเปลี่ยนเนื้อหาข้อความของ element (ส่งผ่านเป็น prop หรืออัปเดต state)
- การสื่อสารระหว่าง component ที่ซับซ้อน (props และ callbacks โดยทั่วไปดีกว่า)
- สถานการณ์ใดๆ ที่คุณพยายามจำลองฟังก์ชันการทำงานของการจัดการ state
เจาะลึก React.createRef(): แนวทางสมัยใหม่สำหรับ Class Components
React.createRef() ถูกนำเสนอใน React 16.3 ซึ่งเป็นวิธีการจัดการ refs ที่ชัดเจนและสะอาดกว่าเมื่อเทียบกับวิธีการเก่าๆ เช่น string refs (ปัจจุบันเลิกใช้แล้ว) และ callback refs (ยังคงใช้ได้แต่บ่อยครั้งจะยืดยาวกว่า) มันถูกออกแบบมาให้เป็นกลไกหลักในการสร้าง ref สำหรับ class components โดยเสนอ API เชิงวัตถุที่เข้ากันได้ดีกับโครงสร้างของคลาส
Syntax และการใช้งานพื้นฐาน: กระบวนการสามขั้นตอน
ขั้นตอนการทำงานสำหรับการใช้ createRef() นั้นตรงไปตรงมาและเกี่ยวข้องกับสามขั้นตอนสำคัญ:
-
สร้าง Ref Object: ใน constructor ของ class component ของคุณ ให้เริ่มต้น instance ของ ref โดยการเรียก
React.createRef()และกำหนดค่าที่ส่งคืนให้กับ instance property (เช่นthis.myRef) -
แนบ Ref: ในเมธอด
renderของ component ของคุณ ให้ส่ง ref object ที่สร้างขึ้นไปยัง attributerefของ React element (ไม่ว่าจะเป็น HTML element หรือ class component) ที่คุณต้องการอ้างอิง -
เข้าถึงเป้าหมาย: เมื่อ component ได้ mount แล้ว DOM node หรือ instance ของ component ที่อ้างอิงจะพร้อมใช้งานผ่าน property
.currentของ ref object ของคุณ (เช่นthis.myRef.current)
import React from 'react';
class FocusInputOnMount extends React.Component {
constructor(props) {
super(props);
this.inputElementRef = React.createRef(); // ขั้นตอนที่ 1: สร้าง ref object ใน constructor
console.log('Constructor: ค่า current ของ Ref ในตอนแรกคือ:', this.inputElementRef.current); // null
}
componentDidMount() {
if (this.inputElementRef.current) {
this.inputElementRef.current.focus();
console.log('ComponentDidMount: โฟกัสที่ Input แล้ว ค่าปัจจุบันคือ:', this.inputElementRef.current.value);
}
}
handleButtonClick = () => {
if (this.inputElementRef.current) {
alert(`ค่าใน Input: ${this.inputElementRef.current.value}`);
}
};
render() {
console.log('Render: ค่า current ของ Ref คือ:', this.inputElementRef.current); // ยังคงเป็น null ในการ render ครั้งแรก
return (
<div style={{ padding: '20px', border: '1px solid #ccc', borderRadius: '8px' }}>
<h3>ช่อง Input ที่โฟกัสอัตโนมัติ</h3>
<label htmlFor="focusInput">กรุณากรอกชื่อของคุณ:</label><br />
<input
id="focusInput"
type="text"
ref={this.inputElementRef} // ขั้นตอนที่ 2: แนบ ref เข้ากับ element <input>
placeholder="ชื่อของคุณ..."
style={{ margin: '10px 0', padding: '8px', borderRadius: '4px', border: '1px solid #ddd' }}
/><br />
<button
onClick={this.handleButtonClick}
style={{ padding: '10px 15px', background: '#007bff', color: 'white', border: 'none', borderRadius: '5px', cursor: 'pointer' }}
>
แสดงค่าใน Input
</button>
<p><em>ช่อง input นี้จะได้รับการโฟกัสโดยอัตโนมัติเมื่อ component โหลด</em></p>
</div>
);
}
}
ในตัวอย่างนี้ this.inputElementRef เป็น object ที่ React จะจัดการภายใน เมื่อ element <input> ถูก render และ mount เข้าสู่ DOM แล้ว React จะกำหนด DOM node จริงนั้นให้กับ this.inputElementRef.current lifecycle method componentDidMount เป็นที่ที่เหมาะสมที่สุดในการโต้ตอบกับ refs เพราะมันรับประกันว่า component และ children ของมันได้ถูก render ลงบน DOM แล้ว และ property .current ก็พร้อมใช้งานและมีค่าแล้ว
การแนบ Ref กับ DOM Element: การเข้าถึง DOM โดยตรง
เมื่อคุณแนบ ref กับ HTML element มาตรฐาน (เช่น <div>, <p>, <button>, <img>) property .current ของ ref object ของคุณจะเก็บ DOM element พื้นฐานจริง สิ่งนี้ให้คุณเข้าถึงเบราว์เซอร์ DOM APIs มาตรฐานทั้งหมดได้อย่างไม่มีข้อจำกัด ช่วยให้คุณสามารถดำเนินการที่ปกติอยู่นอกเหนือการควบคุมแบบ declarative ของ React สิ่งนี้มีประโยชน์อย่างยิ่งสำหรับแอปพลิเคชันระดับโลกที่ต้องการความแม่นยำของเค้าโครง, การเลื่อน, หรือการจัดการโฟกัส ซึ่งอาจมีความสำคัญในสภาพแวดล้อมของผู้ใช้และประเภทอุปกรณ์ที่หลากหลาย
import React from 'react';
class ScrollToElementExample extends React.Component {
constructor(props) {
super(props);
this.targetDivRef = React.createRef();
this.state = { showScrollButton: false };
}
componentDidMount() {
// แสดงปุ่มเลื่อนเฉพาะเมื่อมีเนื้อหามากพอที่จะเลื่อนได้
// การตรวจสอบนี้ยังรับประกันว่า ref มีค่า current แล้ว
if (this.targetDivRef.current && window.innerHeight < document.body.scrollHeight) {
this.setState({ showScrollButton: true });
}
}
handleScrollToTarget = () => {
if (this.targetDivRef.current) {
// ใช้ scrollIntoView สำหรับการเลื่อนที่ราบรื่น ซึ่งรองรับอย่างกว้างขวางทั่วโลกในเบราว์เซอร์ต่างๆ
this.targetDivRef.current.scrollIntoView({
behavior: 'smooth', // ทำให้การเลื่อนเป็นแอนิเมชันเพื่อประสบการณ์ผู้ใช้ที่ดีขึ้น
block: 'start' // จัดตำแหน่งด้านบนของ element ให้ตรงกับด้านบนของ viewport
});
console.log('เลื่อนไปยัง div เป้าหมายแล้ว!');
} else {
console.warn('ยังไม่สามารถเลื่อนไปยัง div เป้าหมายได้');
}
};
render() {
return (
<div style={{ padding: '15px' }}>
<h2>การเลื่อนไปยัง Element ที่เฉพาะเจาะจงด้วย Ref</h2>
<p>ตัวอย่างนี้สาธิตวิธีการเลื่อนไปยัง DOM element ที่อยู่นอกหน้าจอโดยใช้โปรแกรม</p>
{this.state.showScrollButton && (
<button
onClick={this.handleScrollToTarget}
style={{ marginBottom: '20px', padding: '10px 20px', background: '#28a745', color: 'white', border: 'none', borderRadius: '5px', cursor: 'pointer' }}
>
เลื่อนลงไปยังพื้นที่เป้าหมาย
</button>
)}
<div style={{ height: '1500px', background: '#f8f9fa', padding: '20px', marginBottom: '20px', border: '1px dashed #6c757d' }}>
<p>เนื้อหาตัวยึดตำแหน่งเพื่อสร้างพื้นที่เลื่อนในแนวตั้ง</p>
<p>ลองจินตนาการถึงบทความยาวๆ, ฟอร์มที่ซับซ้อน, หรือแดชบอร์ดที่มีรายละเอียดซึ่งต้องการให้ผู้ใช้ต้องนำทางผ่านเนื้อหาที่กว้างขวาง การเลื่อนโดยโปรแกรมช่วยให้ผู้ใช้สามารถไปยังส่วนที่เกี่ยวข้องได้อย่างรวดเร็วโดยไม่ต้องใช้ความพยายามด้วยตนเอง ซึ่งช่วยปรับปรุงการเข้าถึงและขั้นตอนการใช้งานของผู้ใช้ในทุกอุปกรณ์และขนาดหน้าจอ</p>
<p>เทคนิคนี้มีประโยชน์อย่างยิ่งในฟอร์มหลายหน้า, wizards แบบทีละขั้นตอน, หรือแอปพลิเคชันหน้าเดียวที่มีการนำทางลึก</p>
</div>
<div
ref={this.targetDivRef} // แนบ ref ที่นี่
style={{
minHeight: '300px',
background: '#e9ecef',
padding: '30px',
border: '2px solid #007bff',
borderRadius: '10px',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
textAlign: 'center'
}}<br />
>
<h3>คุณมาถึงพื้นที่เป้าหมายแล้ว!</h3>
<p>นี่คือส่วนที่เราเลื่อนมาโดยใช้โปรแกรม</p>
<p>ความสามารถในการควบคุมพฤติกรรมการเลื่อนอย่างแม่นยำเป็นสิ่งสำคัญสำหรับการเพิ่มประสบการณ์ผู้ใช้ โดยเฉพาะอย่างยิ่งบนอุปกรณ์มือถือที่มีพื้นที่หน้าจอจำกัดและการนำทางที่แม่นยำเป็นสิ่งสำคัญอย่างยิ่ง</p>
</div>
</div>
);
}
}
ตัวอย่างนี้แสดงให้เห็นอย่างสวยงามว่า createRef ให้การควบคุมการโต้ตอบระดับเบราว์เซอร์ได้อย่างไร ความสามารถในการเลื่อนโดยโปรแกรมดังกล่าวมีความสำคัญในแอปพลิเคชันจำนวนมาก ตั้งแต่การนำทางเอกสารยาวๆ ไปจนถึงการแนะนำผู้ใช้ผ่านขั้นตอนการทำงานที่ซับซ้อน ตัวเลือก behavior: 'smooth' ใน scrollIntoView ช่วยให้มั่นใจได้ถึงการเปลี่ยนผ่านที่เป็นแอนิเมชันที่น่าพอใจ ซึ่งช่วยเพิ่มประสบการณ์ผู้ใช้ในระดับสากล
การแนบ Ref กับ Class Component: การโต้ตอบกับ Instances
นอกเหนือจาก DOM elements พื้นฐาน คุณยังสามารถแนบ ref กับ instance ของ class component ได้อีกด้วย เมื่อคุณทำเช่นนี้ property .current ของ ref object ของคุณจะเก็บ class component ที่ถูกสร้าง instance ขึ้นมาจริงๆ สิ่งนี้ช่วยให้ parent component สามารถเรียกเมธอดที่กำหนดไว้ภายใน child class component ได้โดยตรง หรือเข้าถึง instance properties ของมันได้ แม้ว่าความสามารถนี้จะมีประสิทธิภาพ แต่ควรใช้อย่างระมัดระวังอย่างยิ่ง เนื่องจากมันอนุญาตให้ทำลายการไหลของข้อมูลทิศทางเดียวแบบดั้งเดิม ซึ่งอาจนำไปสู่พฤติกรรมของแอปพลิเคชันที่คาดเดาได้น้อยลง
import React from 'react';
// Child Class Component
class DialogBox extends React.Component {
constructor(props) {
super(props);
this.state = { isOpen: false, message: '' };
}
// เมธอดที่เปิดเผยให้ parent ผ่าน ref
open(message) {
this.setState({ isOpen: true, message });
}
close = () => {
this.setState({ isOpen: false, message: '' });
};
render() {
if (!this.state.isOpen) return null;
return (
<div style={{
position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%, -50%)',
padding: '25px 35px', background: 'white', border: '1px solid #ddd', borderRadius: '8px',
boxShadow: '0 5px 15px rgba(0,0,0,0.2)', zIndex: 1000, maxWidth: '400px', width: '90%', textAlign: 'center'
}}>
<h4>ข้อความจาก Parent</h4>
<p>{this.state.message}</p>
<button
onClick={this.close}
style={{ marginTop: '15px', padding: '8px 15px', background: '#dc3545', color: 'white', border: 'none', borderRadius: '5px', cursor: 'pointer' }}
>
ปิด
</button>
</div>
);
}
}
// Parent Class Component
class AppWithDialog extends React.Component {
constructor(props) {
super(props);
this.dialogRef = React.createRef();
}
handleOpenDialog = () => {
if (this.dialogRef.current) {
// เข้าถึง instance ของ child component และเรียกเมธอด 'open' ของมัน
this.dialogRef.current.open('สวัสดีจาก parent component! ไดอะล็อกนี้ถูกเปิดแบบ imperative');
}
};
render() {
return (
<div style={{ padding: '20px', textAlign: 'center' }}>
<h2>การสื่อสารระหว่าง Parent-Child ผ่าน Ref</h2>
<p>นี่เป็นการสาธิตว่า parent component สามารถควบคุมเมธอดของ child class component ของมันแบบ imperative ได้อย่างไร</p>
<button
onClick={this.handleOpenDialog}
style={{ padding: '12px 25px', background: '#007bff', color: 'white', border: 'none', borderRadius: '6px', cursor: 'pointer', fontSize: '1.1em' }}
>
เปิดไดอะล็อกแบบ Imperative
</button>
<DialogBox ref={this.dialogRef} /> // แนบ ref กับ instance ของ class component
</div>
);
}
}
ในที่นี้ AppWithDialog สามารถเรียกเมธอด open ของ component DialogBox ได้โดยตรงผ่าน ref ของมัน รูปแบบนี้อาจมีประโยชน์สำหรับการกระตุ้นการกระทำต่างๆ เช่น การแสดง modal, การรีเซ็ตฟอร์ม, หรือการควบคุมองค์ประกอบ UI ภายนอกที่ห่อหุ้มอยู่ภายใน child component อย่างไรก็ตาม โดยทั่วไปแล้วแนะนำให้ใช้การสื่อสารแบบ prop-based สำหรับสถานการณ์ส่วนใหญ่ โดยการส่งข้อมูลและ callbacks ลงมาจาก parent ไปยัง child เพื่อรักษาการไหลของข้อมูลที่ชัดเจนและคาดเดาได้ ควรใช้ refs สำหรับเมธอดของ child component เฉพาะเมื่อการกระทำเหล่านั้นเป็นแบบ imperative อย่างแท้จริงและไม่เข้ากับกระบวนการ prop/state ทั่วไป
การแนบ Ref กับ Functional Component (ความแตกต่างที่สำคัญ)
เป็นความเข้าใจผิดที่พบบ่อย และเป็นจุดที่ต้องแยกแยะให้ชัดเจนว่า คุณ ไม่สามารถแนบ ref โดยตรงโดยใช้ createRef() กับ functional component ได้ Functional components โดยธรรมชาติแล้วไม่มี instances ในลักษณะเดียวกับ class components หากคุณพยายามกำหนด ref โดยตรงให้กับ functional component (เช่น <MyFunctionalComponent ref={this.myRef} />) React จะแสดงคำเตือนในโหมด development เพราะไม่มี instance ของ component ที่จะกำหนดให้กับ .current
หากเป้าหมายของคุณคือการทำให้ parent component (ซึ่งอาจเป็น class component ที่ใช้ createRef หรือ functional component ที่ใช้ useRef) สามารถเข้าถึง DOM element ที่ render อยู่ภายใน functional child component ได้ คุณต้องใช้ React.forwardRef higher-order component นี้ช่วยให้ functional components สามารถเปิดเผย ref ไปยัง DOM node ที่เฉพาะเจาะจงหรือ imperative handle ภายในตัวเองได้
อีกทางเลือกหนึ่ง หากคุณกำลังทำงาน ภายใน functional component และต้องการสร้างและจัดการ ref กลไกที่เหมาะสมคือ useRef hook ซึ่งจะกล่าวถึงสั้นๆ ในส่วนการเปรียบเทียบในภายหลัง สิ่งสำคัญที่ต้องจำไว้คือ createRef นั้นผูกติดกับ class components และธรรมชาติที่อิงกับ instance ของมันโดยพื้นฐาน
การเข้าถึง DOM Node หรือ Component Instance: คำอธิบาย Property `.current`
หัวใจของการโต้ตอบกับ ref อยู่ที่ property .current ของ ref object ที่สร้างขึ้นโดย React.createRef() การทำความเข้าใจวงจรชีวิตของมันและสิ่งที่มันสามารถเก็บไว้ได้นั้นมีความสำคัญอย่างยิ่งต่อการจัดการ ref อย่างมีประสิทธิภาพ
Property `.current`: ประตูสู่การควบคุมแบบ Imperative ของคุณ
property .current เป็น object ที่สามารถเปลี่ยนแปลงค่าได้ซึ่ง React จัดการ มันทำหน้าที่เป็นตัวเชื่อมโยงโดยตรงไปยัง element หรือ instance ของ component ที่ถูกอ้างอิง ค่าของมันจะเปลี่ยนแปลงไปตลอดวงจรชีวิตของ component:
-
การเริ่มต้น: เมื่อคุณเรียก
React.createRef()ครั้งแรกใน constructor, ref object จะถูกสร้างขึ้น และ property.currentของมันจะถูกกำหนดค่าเริ่มต้นเป็นnullนี่เป็นเพราะในขั้นตอนนี้ component ยังไม่ได้ render และยังไม่มี DOM element หรือ instance ของ component ให้ ref ชี้ไป -
การ Mounting: เมื่อ component render ลงบน DOM และ element ที่มี attribute
refถูกสร้างขึ้น React จะกำหนด DOM node จริงหรือ instance ของ class component ให้กับ property.currentของ ref object ของคุณ โดยปกติจะเกิดขึ้นทันทีหลังจากเมธอดrenderเสร็จสิ้นและก่อนที่componentDidMountจะถูกเรียก ดังนั้นcomponentDidMountจึงเป็นที่ที่ปลอดภัยและพบบ่อยที่สุดในการเข้าถึงและโต้ตอบกับ.current -
การ Unmounting: เมื่อ component ถูก unmount ออกจาก DOM, React จะรีเซ็ต property
.currentกลับไปเป็นnullโดยอัตโนมัติ นี่เป็นสิ่งสำคัญในการป้องกัน memory leaks และเพื่อให้แน่ใจว่าแอปพลิเคชันของคุณไม่ได้เก็บการอ้างอิงไปยัง elements ที่ไม่มีอยู่อีกต่อไปใน DOM -
การอัปเดต: ในกรณีที่พบได้น้อยซึ่ง attribute
refถูกเปลี่ยนแปลงบน element ระหว่างการอัปเดต propertycurrentของ ref เก่าจะถูกตั้งค่าเป็นnullก่อนที่ propertycurrentของ ref ใหม่จะถูกตั้งค่า พฤติกรรมนี้พบได้น้อยกว่าแต่สำคัญที่ต้องทราบสำหรับการกำหนด ref แบบไดนามิกที่ซับซ้อน
import React from 'react';
class RefLifecycleLogger extends React.Component {
constructor(props) {
super(props);
this.myDivRef = React.createRef();
console.log('1. Constructor: this.myDivRef.current คือ', this.myDivRef.current); // null
}
componentDidMount() {
console.log('3. componentDidMount: this.myDivRef.current คือ', this.myDivRef.current); // DOM element จริง
if (this.myDivRef.current) {
this.myDivRef.current.style.backgroundColor = '#d4edda'; // การกำหนดสไตล์แบบ imperative เพื่อสาธิต
this.myDivRef.current.innerText += ' - Ref ใช้งานอยู่!';
}
}
componentDidUpdate(prevProps, prevState) {
console.log('4. componentDidUpdate: this.myDivRef.current คือ', this.myDivRef.current); // DOM element จริง (หลังจากการอัปเดต)
}
componentWillUnmount() {
console.log('5. componentWillUnmount: this.myDivRef.current คือ', this.myDivRef.current); // DOM element จริง (ก่อนที่จะเป็น null)
// ณ จุดนี้ คุณอาจทำการ cleanup หากจำเป็น
}
render() {
// ในการ render ครั้งแรก this.myDivRef.current ยังคงเป็น null เพราะ DOM ยังไม่ถูกสร้างขึ้น
// ในการ render ครั้งถัดๆ ไป (หลังจาก mount) มันจะเก็บ element นั้นไว้
console.log('2. Render: this.myDivRef.current คือ', this.myDivRef.current);
return (
<div
ref={this.myDivRef}
style={{ padding: '20px', border: '1px solid #28a745', margin: '20px', minHeight: '80px', display: 'flex', alignItems: 'center' }}
>
<p>นี่คือ div ที่มีการแนบ ref ไว้</p>
</div>
);
}
}
การสังเกต console output ของ RefLifecycleLogger ให้ความเข้าใจที่ชัดเจนว่าเมื่อไหร่ this.myDivRef.current จะพร้อมใช้งาน สิ่งสำคัญคือต้องตรวจสอบเสมอว่า this.myDivRef.current ไม่ใช่ null ก่อนที่จะพยายามโต้ตอบกับมัน โดยเฉพาะอย่างยิ่งในเมธอดที่อาจทำงานก่อนการ mounting หรือหลังการ unmounting
`.current` สามารถเก็บอะไรได้บ้าง? สำรวจเนื้อหาใน Ref ของคุณ
ประเภทของค่าที่ current เก็บไว้ขึ้นอยู่กับว่าคุณแนบ ref กับอะไร:
-
เมื่อแนบกับ HTML element (เช่น
<div>,<input>): property.currentจะมี DOM element พื้นฐานจริงๆ นี่คือ JavaScript object พื้นฐาน ซึ่งให้การเข้าถึง DOM APIs ทั้งหมดของมัน ตัวอย่างเช่น หากคุณแนบ ref กับ<input type="text">,.currentจะเป็นHTMLInputElementobject ซึ่งช่วยให้คุณสามารถเรียกเมธอดต่างๆ เช่น.focus(), อ่าน properties เช่น.value, หรือแก้ไข attributes เช่น.placeholderนี่คือกรณีการใช้งานที่พบบ่อยที่สุดสำหรับ refsthis.inputRef.current.focus();
this.videoRef.current.play();
const { width, height } = this.divRef.current.getBoundingClientRect(); -
เมื่อแนบกับ class component (เช่น
<MyClassComponent />): property.currentจะเก็บ instance ของ class component นั้น ซึ่งหมายความว่าคุณสามารถเรียกเมธอดที่กำหนดไว้ภายใน child component นั้นได้โดยตรง (เช่นchildRef.current.someMethod()) หรือแม้กระทั่งเข้าถึง state หรือ props ของมัน (แม้ว่าการเข้าถึง state/props โดยตรงจาก child ผ่าน ref โดยทั่วไปไม่แนะนำเมื่อเทียบกับการอัปเดต props และ state) ความสามารถนี้มีประโยชน์สำหรับการกระตุ้นพฤติกรรมเฉพาะใน child components ที่ไม่เข้ากับโมเดลการโต้ตอบแบบ prop-based มาตรฐานthis.childComponentRef.current.resetForm();
// ไม่บ่อยนัก แต่เป็นไปได้: console.log(this.childComponentRef.current.state.someValue); -
เมื่อแนบกับ functional component (ผ่าน
forwardRef): ดังที่ได้กล่าวไว้ก่อนหน้านี้ refs ไม่สามารถแนบโดยตรงกับ functional components ได้ อย่างไรก็ตาม หาก functional component ถูกห่อด้วยReact.forwardRef, property.currentจะเก็บค่าใดก็ตามที่ functional component เปิดเผยอย่างชัดเจนผ่าน ref ที่ส่งต่อมา ซึ่งโดยทั่วไปจะเป็น DOM element ภายใน functional component หรือ object ที่มีเมธอดแบบ imperative (โดยใช้useImperativeHandlehook ร่วมกับforwardRef)// ใน parent, myForwardedRef.current จะเป็น DOM node หรือ object ที่ถูกเปิดเผย
this.myForwardedRef.current.focus();
this.myForwardedRef.current.customResetMethod();
กรณีการใช้งานจริงของ `createRef`
เพื่อให้เข้าใจถึงประโยชน์ของ React.createRef() อย่างแท้จริง เรามาสำรวจสถานการณ์ที่เกี่ยวข้องกับระดับโลกและมีรายละเอียดมากขึ้นซึ่งพิสูจน์ให้เห็นว่ามันขาดไม่ได้ โดยก้าวข้ามการจัดการโฟกัสแบบง่ายๆ
1. การจัดการโฟกัส, การเลือกข้อความ, หรือการเล่นสื่อข้ามวัฒนธรรม
นี่คือตัวอย่างสำคัญของการโต้ตอบ UI แบบ imperative ลองจินตนาการถึงฟอร์มหลายขั้นตอนที่ออกแบบมาสำหรับผู้ชมทั่วโลก หลังจากผู้ใช้ทำส่วนหนึ่งเสร็จ คุณอาจต้องการย้ายโฟกัสไปยัง input แรกของส่วนถัดไปโดยอัตโนมัติ โดยไม่คำนึงถึงภาษาหรือทิศทางของข้อความเริ่มต้น (ซ้ายไปขวา หรือ ขวาไปซ้าย) Refs ให้การควบคุมที่จำเป็น
import React from 'react';
class DynamicFocusForm extends React.Component {
constructor(props) {
super(props);
this.firstNameRef = React.createRef();
this.lastNameRef = React.createRef();
this.emailRef = React.createRef();
this.state = { currentStep: 1 };
}
componentDidMount() {
// โฟกัสที่ input แรกเมื่อ component mount
this.firstNameRef.current.focus();
}
handleNextStep = (nextRef) => {
this.setState(prevState => ({ currentStep: prevState.currentStep + 1 }), () => {
// หลังจาก state อัปเดตและ component re-render ให้โฟกัสที่ input ถัดไป
if (nextRef.current) {
nextRef.current.focus();
}
});
};
render() {
const { currentStep } = this.state;
const formSectionStyle = { border: '1px solid #0056b3', padding: '20px', margin: '15px 0', borderRadius: '8px', background: '#e7f0fa' };
const inputStyle = { width: '100%', padding: '10px', margin: '8px 0', border: '1px solid #ccc', borderRadius: '4px' };
const buttonStyle = { padding: '10px 20px', background: '#007bff', color: 'white', border: 'none', borderRadius: '5px', cursor: 'pointer', marginTop: '10px' };
return (
<div style={{ maxWidth: '600px', margin: '30px auto', padding: '25px', boxShadow: '0 4px 12px rgba(0,0,0,0.1)', borderRadius: '10px', background: 'white' }}>
<h2>ฟอร์มหลายขั้นตอนพร้อมการจัดการโฟกัสด้วย Ref</h2>
<p>ขั้นตอนปัจจุบัน: <strong>{currentStep}</strong></p>
{currentStep === 1 && (
<div style={formSectionStyle}>
<h3>รายละเอียดส่วนตัว</h3>
<label htmlFor="firstName">ชื่อจริง:</label>
<input id="firstName" type="text" ref={this.firstNameRef} style={inputStyle} placeholder="เช่น สมชาย" />
<label htmlFor="lastName">นามสกุล:</label>
<input id="lastName" type="text" ref={this.lastNameRef} style={inputStyle} placeholder="เช่น ใจดี" />
<button onClick={() => this.handleNextStep(this.emailRef)} style={buttonStyle}>ถัดไป →</button>
</div>
)}
{currentStep === 2 && (
<div style={formSectionStyle}>
<h3>ข้อมูลการติดต่อ</h3>
<label htmlFor="email">อีเมล:</label>
<input id="email" type="email" ref={this.emailRef} style={inputStyle} placeholder="เช่น somchai.j@example.com" />
<p>... ช่องข้อมูลการติดต่ออื่นๆ ...</p>
<button onClick={() => alert('ส่งฟอร์มสำเร็จ!')} style={buttonStyle}>ส่งข้อมูล</button>
</div>
)}
<p><em>การโต้ตอบนี้ช่วยเพิ่มการเข้าถึงและประสบการณ์ผู้ใช้อย่างมาก โดยเฉพาะสำหรับผู้ใช้ที่ต้องพึ่งพาการนำทางด้วยคีย์บอร์ดหรือเทคโนโลยีช่วยเหลือต่างๆ ทั่วโลก</em></p>
</div>
);
}
}
ตัวอย่างนี้สาธิตฟอร์มหลายขั้นตอนที่ใช้งานได้จริงซึ่งใช้ createRef เพื่อจัดการโฟกัสโดยโปรแกรม สิ่งนี้ช่วยให้มั่นใจได้ถึงการเดินทางของผู้ใช้ที่ราบรื่นและเข้าถึงได้ ซึ่งเป็นข้อพิจารณาที่สำคัญสำหรับแอปพลิเคชันที่ใช้ในบริบททางภาษาและวัฒนธรรมที่หลากหลาย ในทำนองเดียวกัน สำหรับเครื่องเล่นสื่อ refs ช่วยให้คุณสามารถสร้างส่วนควบคุมที่กำหนดเอง (เล่น, หยุดชั่วคราว, ระดับเสียง, ค้นหา) ที่โต้ตอบโดยตรงกับ APIs ดั้งเดิมของ element <video> หรือ <audio> ของ HTML5 ซึ่งให้ประสบการณ์ที่สอดคล้องกันโดยไม่ขึ้นอยู่กับค่าเริ่มต้นของเบราว์เซอร์
2. การกระตุ้นแอนิเมชันแบบ Imperative และการโต้ตอบกับ Canvas
ในขณะที่ไลบรารีแอนิเมชันแบบ declarative เหมาะสำหรับเอฟเฟกต์ UI จำนวนมาก แต่แอนิเมชันขั้นสูงบางอย่าง โดยเฉพาะอย่างยิ่งที่ใช้ประโยชน์จาก HTML5 Canvas API, WebGL, หรือต้องการการควบคุมคุณสมบัติของ element อย่างละเอียดซึ่งจัดการได้ดีที่สุดนอกวงจรการ render ของ React จะได้รับประโยชน์อย่างมากจาก refs ตัวอย่างเช่น การสร้างภาพข้อมูลแบบเรียลไทม์หรือเกมบน Canvas element เกี่ยวข้องกับการวาดลงบนบัฟเฟอร์พิกเซลโดยตรง ซึ่งเป็นกระบวนการแบบ imperative โดยเนื้อแท้
import React from 'react';
class CanvasAnimator extends React.Component {
constructor(props) {
super(props);
this.canvasRef = React.createRef();
this.animationFrameId = null;
}
componentDidMount() {
this.startAnimation();
}
componentWillUnmount() {
this.stopAnimation();
}
startAnimation = () => {
const canvas = this.canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext('2d');
let angle = 0;
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const radius = 50;
const animate = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height); // ล้าง canvas
// วาดสี่เหลี่ยมที่หมุนได้
ctx.save();
ctx.translate(centerX, centerY);
ctx.rotate(angle);
ctx.fillStyle = '#6f42c1';
ctx.fillRect(-radius / 2, -radius / 2, radius, radius);
ctx.restore();
angle += 0.05; // เพิ่มมุมสำหรับการหมุน
this.animationFrameId = requestAnimationFrame(animate);
};
this.animationFrameId = requestAnimationFrame(animate);
};
stopAnimation = () => {
if (this.animationFrameId) {
cancelAnimationFrame(this.animationFrameId);
}
};
render() {
return (
<div style={{ textAlign: 'center', margin: '30px auto', border: '1px solid #ced4da', padding: '20px', borderRadius: '8px', background: '#f8f9fa' }}>
<h3>แอนิเมชัน Canvas แบบ Imperative ด้วย createRef</h3>
<p>แอนิเมชัน canvas นี้ถูกควบคุมโดยตรงโดยใช้ APIs ของเบราว์เซอร์ผ่าน ref</p>
<canvas ref={this.canvasRef} width="300" height="200" style={{ border: '1px solid #adb5bd', background: 'white' }}>
เบราว์เซอร์ของคุณไม่รองรับแท็ก canvas ของ HTML5
</canvas>
<p><em>การควบคุมโดยตรงเช่นนี้มีความสำคัญสำหรับกราฟิกประสิทธิภาพสูง, เกม, หรือการสร้างภาพข้อมูลพิเศษที่ใช้ในอุตสาหกรรมต่างๆ ทั่วโลก</em></p>
</div>
);
}
}
component นี้ให้ canvas element และใช้ ref เพื่อเข้าถึง context การ render 2D ของมันโดยตรง วงจรแอนิเมชันที่ขับเคลื่อนโดย `requestAnimationFrame` จากนั้นจะวาดและอัปเดตสี่เหลี่ยมที่หมุนได้แบบ imperative รูปแบบนี้เป็นพื้นฐานสำหรับการสร้างแดชบอร์ดข้อมูลแบบโต้ตอบ, เครื่องมือออกแบบออนไลน์, หรือแม้แต่เกมง่ายๆ ที่ต้องการการ render ที่แม่นยำแบบเฟรมต่อเฟรม โดยไม่คำนึงถึงตำแหน่งทางภูมิศาสตร์หรือความสามารถของอุปกรณ์ของผู้ใช้
3. การผสานรวมกับไลบรารี DOM ของบุคคลที่สาม: สะพานเชื่อมที่ไร้รอยต่อ
หนึ่งในเหตุผลที่น่าสนใจที่สุดในการใช้ refs คือการผสานรวม React กับไลบรารี JavaScript ภายนอกที่จัดการ DOM โดยตรง ไลบรารีที่มีประสิทธิภาพจำนวนมาก โดยเฉพาะอย่างยิ่งไลบรารีรุ่นเก่าหรือที่เน้นงาน render เฉพาะทาง (เช่น การสร้างแผนภูมิ, แผนที่, หรือการแก้ไขข้อความ rich text) ทำงานโดยรับ DOM element เป็นเป้าหมายแล้วจัดการเนื้อหาของมันด้วยตัวเอง React ในโหมด declarative ของมันอาจขัดแย้งกับไลบรารีเหล่านี้โดยพยายามควบคุม DOM subtree เดียวกัน Refs ป้องกันความขัดแย้งนี้โดยการให้ 'container' ที่กำหนดไว้สำหรับไลบรารีภายนอก
import React from 'react';
import * as d3 from 'd3'; // สมมติว่า D3.js ได้รับการติดตั้งและ import แล้ว
class D3BarChart extends React.Component {
constructor(props) {
super(props);
this.chartContainerRef = React.createRef();
}
// เมื่อ component mount ให้วาดแผนภูมิ
componentDidMount() {
this.drawChart();
}
// เมื่อ component อัปเดต (เช่น props.data เปลี่ยนแปลง) ให้อัปเดตแผนภูมิ
componentDidUpdate(prevProps) {
if (prevProps.data !== this.props.data) {
this.drawChart();
}
}
// เมื่อ component unmount ให้ล้าง elements ของ D3 เพื่อป้องกัน memory leaks
componentWillUnmount() {
d3.select(this.chartContainerRef.current).selectAll('*').remove();
}
drawChart = () => {
const data = this.props.data || [40, 80, 20, 100, 60, 90]; // ข้อมูลเริ่มต้น
const node = this.chartContainerRef.current;
if (!node) return; // ตรวจสอบให้แน่ใจว่า ref พร้อมใช้งาน
// ล้าง elements ของแผนภูมิเก่าที่ D3 วาดไว้
d3.select(node).selectAll('*').remove();
const margin = { top: 20, right: 20, bottom: 30, left: 40 };
const width = 460 - margin.left - margin.right;
const height = 300 - margin.top - margin.bottom;
const svg = d3.select(node)
.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);
// ตั้งค่า scales
const x = d3.scaleBand()
.range([0, width])
.padding(0.1);
const y = d3.scaleLinear()
.range([height, 0]);
x.domain(data.map((d, i) => i)); // ใช้ index เป็น domain เพื่อความง่าย
y.domain([0, d3.max(data)]);
// เพิ่มแท่งกราฟ
svg.selectAll('.bar')
.data(data)
.enter().append('rect')
.attr('class', 'bar')
.attr('x', (d, i) => x(i))
.attr('width', x.bandwidth())
.attr('y', d => y(d))
.attr('height', d => height - y(d))
.attr('fill', '#17a2b8');
// เพิ่มแกน X
svg.append('g')
.attr('transform', `translate(0,${height})`)
.call(d3.axisBottom(x));
// เพิ่มแกน Y
svg.append('g')
.call(d3.axisLeft(y));
};
render() {
return (
<div style={{ textAlign: 'center', margin: '30px auto', border: '1px solid #00a0b2', padding: '20px', borderRadius: '8px', background: '#e0f7fa' }}>
<h3>การผสานรวมแผนภูมิ D3.js กับ React createRef</h3>
<p>ภาพข้อมูลนี้ถูก render โดย D3.js ภายใน container ที่จัดการโดย React</p>
<div ref={this.chartContainerRef} /> // D3.js จะ render ลงใน div นี้
<p><em>การผสานรวมไลบรารีพิเศษเช่นนี้มีความสำคัญสำหรับแอปพลิเคชันที่เน้นข้อมูล ซึ่งมอบเครื่องมือวิเคราะห์ที่มีประสิทธิภาพให้กับผู้ใช้ในอุตสาหกรรมและภูมิภาคต่างๆ</em></p>
</div>
);
}
}
ตัวอย่างที่ครอบคลุมนี้แสดงการผสานรวมแผนภูมิแท่งของ D3.js ภายใน React class component chartContainerRef ให้ D3.js มี DOM node ที่ต้องการเพื่อทำการ render React จัดการวงจรชีวิตของ container <div> ในขณะที่ D3.js จัดการเนื้อหาภายในของมัน เมธอด `componentDidUpdate` และ `componentWillUnmount` มีความสำคัญอย่างยิ่งสำหรับการอัปเดตแผนภูมิเมื่อข้อมูลเปลี่ยนแปลงและสำหรับการ cleanup ที่จำเป็น เพื่อป้องกัน memory leaks และรับประกันประสบการณ์ที่ตอบสนองได้ดี รูปแบบนี้สามารถนำไปใช้ได้ในระดับสากล ช่วยให้นักพัฒนาสามารถใช้ประโยชน์จากสิ่งที่ดีที่สุดของทั้งโมเดล component ของ React และไลบรารีการสร้างภาพข้อมูลพิเศษที่มีประสิทธิภาพสูงสำหรับแดชบอร์ดและแพลตฟอร์มการวิเคราะห์ระดับโลก
4. การวัดขนาดหรือตำแหน่งของ Element สำหรับเค้าโครงแบบไดนามิก
สำหรับเค้าโครงที่มีไดนามิกสูงหรือตอบสนองได้ดี หรือสำหรับการนำฟีเจอร์ต่างๆ เช่น virtualized lists ที่ render เฉพาะรายการที่มองเห็นได้ การทราบขนาดและตำแหน่งที่แม่นยำของ elements เป็นสิ่งสำคัญ Refs ช่วยให้คุณเข้าถึงเมธอด getBoundingClientRect() ซึ่งให้ข้อมูลที่สำคัญนี้โดยตรงจาก DOM
import React from 'react';
class ElementDimensionLogger extends React.Component {
constructor(props) {
super(props);
this.measurableDivRef = React.createRef();
this.state = {
width: 0,
height: 0,
top: 0,
left: 0,
message: 'คลิกปุ่มเพื่อวัดขนาด!'
};
}
componentDidMount() {
// การวัดครั้งแรกมักมีประโยชน์ แต่ก็สามารถถูกกระตุ้นโดยการกระทำของผู้ใช้ได้เช่นกัน
this.measureElement();
// สำหรับเค้าโครงแบบไดนามิก คุณอาจต้องฟังเหตุการณ์การปรับขนาดหน้าต่าง
window.addEventListener('resize', this.measureElement);
}
componentWillUnmount() {
window.removeEventListener('resize', this.measureElement);
}
measureElement = () => {
if (this.measurableDivRef.current) {
const rect = this.measurableDivRef.current.getBoundingClientRect();
this.setState({
width: Math.round(rect.width),
height: Math.round(rect.height),
top: Math.round(rect.top),
left: Math.round(rect.left),
message: 'อัปเดตขนาดแล้ว'
});
} else {
this.setState({ message: 'ยังไม่ได้ render element' });
}
};
render() {
const { width, height, top, left, message } = this.state;
const boxStyle = {
width: '70%',
minHeight: '150px',
border: '3px solid #ffc107',
margin: '25px auto',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
background: '#fff3cd',
borderRadius: '8px',
textAlign: 'center'
};
return (
<div style={{ maxWidth: '700px', margin: '30px auto', padding: '25px', boxShadow: '0 4px 12px rgba(0,0,0,0.08)', borderRadius: '10px', background: 'white' }}>
<h3>การวัดขนาด Element ด้วย createRef</h3>
<p>ตัวอย่างนี้ดึงและแสดงขนาดและตำแหน่งของ element เป้าหมายแบบไดนามิก</p>
<div ref={this.measurableDivRef} style={boxStyle}>
<p><strong>ฉันคือ element ที่กำลังถูกวัด</strong></p>
<p>ปรับขนาดหน้าต่างเบราว์เซอร์ของคุณเพื่อดูการวัดที่เปลี่ยนแปลงเมื่อรีเฟรช/กระตุ้นด้วยตนเอง</p>
</div>
<button
onClick={this.measureElement}
style={{ padding: '10px 20px', background: '#6c757d', color: 'white', border: 'none', borderRadius: '5px', cursor: 'pointer', marginBottom: '15px' }}
>
วัดตอนนี้
</button>
<div style={{ background: '#f0f0f0', padding: '15px', borderRadius: '6px' }}>
<p><strong>ขนาดสด:</strong></p>
<ul style={{ listStyleType: 'none', padding: 0, textAlign: 'left', margin: '0 auto', maxWidth: '300px' }}>
<li>ความกว้าง: <b>{width}px</b></li>
<li>ความสูง: <b>{height}px</b></li>
<li>ตำแหน่งบน (Viewport): <b>{top}px</b></li>
<li>ตำแหน่งซ้าย (Viewport): <b>{left}px</b></li>
</ul>
<p><em>การวัด element ที่แม่นยำมีความสำคัญต่อการออกแบบที่ตอบสนองได้ดีและการเพิ่มประสิทธิภาพบนอุปกรณ์ที่หลากหลายทั่วโลก</em></p>
</div>
</div>
);
}
}
component นี้ใช้ createRef เพื่อรับ getBoundingClientRect() ของ div element ซึ่งให้ขนาดและตำแหน่งตามเวลาจริง ข้อมูลนี้มีค่าอย่างยิ่งสำหรับการปรับเค้าโครงที่ซับซ้อน, การกำหนดการมองเห็นใน virtualized scroll list, หรือแม้แต่เพื่อให้แน่ใจว่า elements อยู่ในพื้นที่ viewport ที่เฉพาะเจาะจง สำหรับผู้ชมทั่วโลกที่ขนาดหน้าจอ, ความละเอียด, และสภาพแวดล้อมของเบราว์เซอร์แตกต่างกันอย่างมาก การควบคุมเค้าโครงที่แม่นยำโดยอิงจากการวัด DOM จริงเป็นปัจจัยสำคัญในการมอบประสบการณ์ผู้ใช้ที่สอดคล้องและมีคุณภาพสูง
แนวทางปฏิบัติที่ดีที่สุดและข้อควรระวังในการใช้ `createRef`
ในขณะที่ createRef ให้การควบคุมแบบ imperative ที่ทรงพลัง การใช้งานที่ไม่ถูกต้องอาจนำไปสู่โค้ดที่จัดการและดีบักได้ยาก การปฏิบัติตามแนวทางปฏิบัติที่ดีที่สุดเป็นสิ่งจำเป็นสำหรับการใช้พลังของมันอย่างมีความรับผิดชอบ
1. ให้ความสำคัญกับแนวทางแบบ Declarative: กฎทอง
จำไว้เสมอว่า refs เป็น "ช่องทางหลีกหนี" ไม่ใช่โหมดหลักของการโต้ตอบใน React ก่อนที่จะใช้ ref ให้ถามตัวเองว่า: สิ่งนี้สามารถทำได้ด้วย state และ props หรือไม่? หากคำตอบคือใช่ นั่นเกือบจะเป็นแนวทางที่ดีกว่าและเป็น "React-idiomatic" มากกว่าเสมอ ตัวอย่างเช่น หากคุณต้องการเปลี่ยนค่าของ input ให้ใช้ controlled components กับ state ไม่ใช่ ref เพื่อตั้งค่า inputRef.current.value โดยตรง
2. Refs มีไว้สำหรับการโต้ตอบแบบ Imperative ไม่ใช่การจัดการ State
Refs เหมาะสมที่สุดสำหรับงานที่เกี่ยวข้องกับการกระทำโดยตรงแบบ imperative บน DOM elements หรือ component instances มันเป็นคำสั่ง: "โฟกัส input นี้," "เล่นวิดีโอนี้," "เลื่อนไปยังส่วนนี้" มันไม่ได้มีไว้สำหรับการเปลี่ยนแปลง UI แบบ declarative ของ component ตาม state การจัดการสไตล์หรือเนื้อหาของ element โดยตรงผ่าน ref เมื่อสิ่งนั้นสามารถควบคุมได้โดย props หรือ state อาจนำไปสู่การที่ virtual DOM ของ React ไม่ซิงค์กับ DOM จริง ทำให้เกิดพฤติกรรมที่คาดเดาไม่ได้และปัญหาการ render
3. Refs และ Functional Components: โอบรับ `useRef` และ `forwardRef`
สำหรับการพัฒนา React สมัยใหม่ภายใน functional components, React.createRef() ไม่ใช่เครื่องมือที่คุณจะใช้ แต่คุณจะพึ่งพา useRef hook แทน useRef hook ให้ ref object ที่เปลี่ยนแปลงค่าได้คล้ายกับ createRef ซึ่ง property .current ของมันสามารถใช้สำหรับการโต้ตอบแบบ imperative แบบเดียวกันได้ มันรักษามูลค่าของมันข้ามการ re-render ของ component โดยไม่ทำให้เกิดการ re-render เอง ทำให้มันเหมาะอย่างยิ่งสำหรับการเก็บการอ้างอิงไปยัง DOM node หรือค่าที่เปลี่ยนแปลงได้ใดๆ ที่ต้องคงอยู่ข้ามการ renders
import React, { useRef, useEffect } from 'react';
function FunctionalComponentWithRef() {
const myInputRef = useRef(null); // เริ่มต้นด้วย null
useEffect(() => {
// สิ่งนี้จะทำงานหลังจาก component mount
if (myInputRef.current) {
myInputRef.current.focus();
console.log('Input ของ functional component ได้รับการโฟกัสแล้ว!');
}
}, []); // dependency array ที่ว่างเปล่าช่วยให้มั่นใจว่ามันทำงานเพียงครั้งเดียวเมื่อ mount
const handleLogValue = () => {
if (myInputRef.current) {
alert(`ค่าใน Input: ${myInputRef.current.value}`);
}
};
return (
<div style={{ margin: '20px', padding: '20px', border: '1px solid #009688', borderRadius: '8px', background: '#e0f2f1' }}>
<h3>การใช้ useRef ใน Functional Component</h3>
<label htmlFor="funcInput">พิมพ์อะไรบางอย่าง:</label><br />
<input id="funcInput" type="text" ref={myInputRef} placeholder="ฉันถูกโฟกัสอัตโนมัติ!" style={{ padding: '8px', margin: '10px 0', borderRadius: '4px', border: '1px solid #ccc' }} /><br />
<button onClick={handleLogValue} style={{ padding: '10px 15px', background: '#009688', color: 'white', border: 'none', borderRadius: '5px', cursor: 'pointer' }}>
แสดงค่าใน Input
</button>
<p><em>สำหรับโปรเจกต์ใหม่ `useRef` เป็นตัวเลือกที่เหมาะสมสำหรับการใช้ refs ใน functional components</em></p>
</div>
);
}
หากคุณต้องการให้ parent component ได้รับ ref ไปยัง DOM element ภายใน functional child component, React.forwardRef คือโซลูชันของคุณ มันเป็น higher-order component ที่ช่วยให้คุณสามารถ "ส่งต่อ" ref จาก parent ไปยังหนึ่งใน DOM elements ของ children ของมัน โดยยังคงรักษาการห่อหุ้มของ functional component ไว้ในขณะที่ยังคงเปิดใช้งานการเข้าถึงแบบ imperative เมื่อจำเป็น
import React, { useRef, useEffect } from 'react';
// Functional component ที่ส่งต่อ ref ไปยัง input element ของมันอย่างชัดเจน
const ForwardedInput = React.forwardRef((props, ref) => (
<input type="text" ref={ref} className="forwarded-input" placeholder={props.placeholder} style={{ padding: '10px', margin: '8px 0', border: '1px solid #ccc', borderRadius: '4px', width: '100%' }} />
));
class ParentComponentUsingForwardRef extends React.Component {
constructor(props) {
super(props);
this.parentInputRef = React.createRef();
}
componentDidMount() {
if (this.parentInputRef.current) {
this.parentInputRef.current.focus();
console.log('Input ภายใน functional component ได้รับการโฟกัสจาก parent (class component) ผ่าน forwarded ref!');
}
}
render() {
return (
<div style={{ margin: '20px', padding: '20px', border: '1px solid #6f42c1', borderRadius: '8px', background: '#f5eef9' }}>
<h3>ตัวอย่าง Ref Forwarding ด้วย createRef (Parent Class Component)</h3>
<label>กรอกรายละเอียด:</label>
<ForwardedInput ref={this.parentInputRef} placeholder="Input นี้อยู่ภายใน functional component" />
<p><em>รูปแบบนี้มีความสำคัญอย่างยิ่งสำหรับการสร้างไลบรารี component ที่ใช้ซ้ำได้ซึ่งจำเป็นต้องเปิดเผยการเข้าถึง DOM โดยตรง</em></p>
</div>
);
}
}
นี่แสดงให้เห็นว่า class component ที่ใช้ createRef สามารถโต้ตอบกับ DOM element ที่ซ้อนอยู่ภายใน functional component ได้อย่างมีประสิทธิภาพโดยใช้ forwardRef สิ่งนี้ทำให้ functional components มีความสามารถเท่าเทียมกันในการมีส่วนร่วมในการโต้ตอบแบบ imperative เมื่อจำเป็น ทำให้มั่นใจได้ว่าโค้ดเบสของ React สมัยใหม่ยังคงสามารถได้รับประโยชน์จาก refs ได้
4. เมื่อไม่ควรใช้ Refs: การรักษาความสมบูรณ์ของ React
- สำหรับการควบคุม state ของ child component: อย่าใช้ ref เพื่ออ่านหรืออัปเดต state ของ child component โดยตรง สิ่งนี้ข้ามการจัดการ state ของ React ทำให้แอปพลิเคชันของคุณคาดเดาไม่ได้ แต่ให้ส่ง state ลงมาเป็น props และใช้ callbacks เพื่อให้ children สามารถร้องขอการเปลี่ยนแปลง state จาก parents ได้
- เพื่อทดแทน props: ในขณะที่คุณ สามารถ เรียกเมธอดบน child class component ผ่าน ref ได้ ให้พิจารณาว่าการส่ง event handler เป็น prop ไปยัง child จะบรรลุเป้าหมายเดียวกันในลักษณะที่เป็น "React-idiomatic" มากกว่าหรือไม่ Props ส่งเสริมการไหลของข้อมูลที่ชัดเจนและทำให้การโต้ตอบของ component โปร่งใส
-
สำหรับการจัดการ DOM ง่ายๆ ที่ React สามารถทำได้: หากคุณต้องการเปลี่ยนข้อความ, สไตล์, หรือเพิ่ม/ลบคลาสของ element ตาม state ให้ทำแบบ declarative ตัวอย่างเช่น เพื่อสลับคลาส
activeให้ใช้เงื่อนไขใน JSX:<div className={isActive ? 'active' : ''}>แทนที่จะเป็นdivRef.current.classList.add('active')
5. ข้อพิจารณาด้านประสิทธิภาพและการเข้าถึงทั่วโลก
ในขณะที่ createRef เองนั้นมีประสิทธิภาพ แต่การดำเนินการที่ทำโดยใช้ current อาจมีผลกระทบด้านประสิทธิภาพอย่างมีนัยสำคัญ สำหรับผู้ใช้บนอุปกรณ์ระดับล่างหรือการเชื่อมต่อเครือข่ายที่ช้า (ซึ่งพบบ่อยในหลายส่วนของโลก) การจัดการ DOM ที่ไม่มีประสิทธิภาพอาจนำไปสู่การกระตุก, UI ที่ไม่ตอบสนอง, และประสบการณ์ผู้ใช้ที่ไม่ดี เมื่อใช้ refs สำหรับงานต่างๆ เช่น แอนิเมชัน, การคำนวณเค้าโครงที่ซับซ้อน, หรือการผสานรวมกับไลบรารีของบุคคลที่สามที่มีขนาดใหญ่:
-
Debounce/Throttle Events: หากคุณกำลังใช้ refs เพื่อวัดขนาดในเหตุการณ์
window.resizeหรือscrollให้แน่ใจว่า handlers เหล่านี้ถูก debounce หรือ throttled เพื่อป้องกันการเรียกฟังก์ชันและการอ่าน DOM ที่มากเกินไป -
Batch DOM Reads/Writes: หลีกเลี่ยงการสลับการดำเนินการอ่าน DOM (เช่น
getBoundingClientRect()) กับการดำเนินการเขียน DOM (เช่น การตั้งค่าสไตล์) สิ่งนี้อาจทำให้เกิด layout thrashing เครื่องมืออย่างfastdomสามารถช่วยจัดการสิ่งนี้ได้อย่างมีประสิทธิภาพ -
เลื่อนการดำเนินการที่ไม่สำคัญ: ใช้
requestAnimationFrameสำหรับแอนิเมชัน และsetTimeout(..., 0)หรือrequestIdleCallbackสำหรับการจัดการ DOM ที่มีความสำคัญน้อยกว่า เพื่อให้แน่ใจว่าพวกมันไม่บล็อกเธรดหลักและส่งผลกระทบต่อการตอบสนอง - เลือกอย่างชาญฉลาด: บางครั้งประสิทธิภาพของไลบรารีของบุคคลที่สามอาจเป็นคอขวด ประเมินทางเลือกอื่นหรือพิจารณาการ lazy-loading components ดังกล่าวสำหรับผู้ใช้ที่มีการเชื่อมต่อที่ช้ากว่า เพื่อให้แน่ใจว่าประสบการณ์พื้นฐานยังคงมีประสิทธิภาพทั่วโลก
`createRef` vs. Callback Refs vs. `useRef`: การเปรียบเทียบโดยละเอียด
React ได้เสนอวิธีการต่างๆ ในการจัดการ refs ตลอดวิวัฒนาการของมัน การทำความเข้าใจความแตกต่างของแต่ละวิธีเป็นกุญแจสำคัญในการเลือกวิธีที่เหมาะสมที่สุดสำหรับบริบทเฉพาะของคุณ
1. `React.createRef()` (Class Components - สมัยใหม่)
-
กลไก: สร้าง ref object (
{ current: null }) ใน constructor ของ instance ของ component React จะกำหนด DOM element หรือ component instance ให้กับ property.currentหลังจากการ mounting - การใช้งานหลัก: เฉพาะภายใน class components เท่านั้น มันถูกเริ่มต้นเพียงครั้งเดียวต่อ instance ของ component
-
การกำหนดค่า Ref:
.currentจะถูกตั้งค่าเป็น element/instance หลังจากที่ component mount และรีเซ็ตเป็นnullเมื่อ unmount - เหมาะที่สุดสำหรับ: ความต้องการ ref มาตรฐานทั้งหมดใน class components ที่คุณต้องการอ้างอิงถึง DOM element หรือ instance ของ child class component
- ข้อดี: синтаксис เชิงวัตถุที่ชัดเจนและตรงไปตรงมา ไม่ต้องกังวลเกี่ยวกับการสร้างฟังก์ชัน inline ซ้ำๆ ที่ทำให้เกิดการเรียกพิเศษ (อย่างที่อาจเกิดขึ้นกับ callback refs)
- ข้อเสีย: ไม่สามารถใช้กับ functional components ได้ หากไม่ได้เริ่มต้นใน constructor (เช่น ใน render) ref object ใหม่อาจถูกสร้างขึ้นทุกครั้งที่ render ซึ่งนำไปสู่ปัญหาด้านประสิทธิภาพหรือค่า ref ที่ไม่ถูกต้อง ต้องจำไว้เสมอว่าต้องกำหนดให้กับ instance property
2. Callback Refs (Class & Functional Components - ยืดหยุ่น/ดั้งเดิม)
-
กลไก: คุณส่งฟังก์ชันโดยตรงไปยัง prop
refReact จะเรียกฟังก์ชันนี้พร้อมกับ DOM element หรือ component instance ที่ mount แล้ว และหลังจากนั้นจะเรียกพร้อมกับnullเมื่อมันถูก unmount -
การใช้งานหลัก: สามารถใช้ได้ทั้งใน class และ functional components ใน class components callback มักจะถูกผูกกับ
thisหรือกำหนดเป็น arrow function class property ใน functional components มักจะถูกกำหนดแบบ inline หรือ memoized -
การกำหนดค่า Ref: ฟังก์ชัน callback จะถูกเรียกโดย React โดยตรง คุณมีหน้าที่รับผิดชอบในการจัดเก็บการอ้างอิง (เช่น
this.myInput = element;) -
เหมาะที่สุดสำหรับ: สถานการณ์ที่ต้องการการควบคุมที่ละเอียดกว่าเมื่อ refs ถูกตั้งค่าและยกเลิก หรือสำหรับรูปแบบขั้นสูงเช่นรายการ ref แบบไดนามิก มันเป็นวิธีหลักในการจัดการ refs ก่อนที่จะมี
createRefและuseRef - ข้อดี: ให้ความยืดหยุ่นสูงสุด ให้คุณเข้าถึง ref ได้ทันทีเมื่อมันพร้อมใช้งาน (ภายในฟังก์ชัน callback) สามารถใช้เพื่อเก็บ refs ใน array หรือ map สำหรับคอลเลกชันของ elements แบบไดนามิก
-
ข้อเสีย: หาก callback ถูกกำหนดแบบ inline ภายในเมธอด
render(เช่นref={el => this.myRef = el}) มันจะถูกเรียกสองครั้งระหว่างการอัปเดต (ครั้งแรกกับnull, จากนั้นกับ element) ซึ่งอาจทำให้เกิดปัญหาด้านประสิทธิภาพหรือผลข้างเคียงที่ไม่คาดคิดหากไม่จัดการอย่างระมัดระวัง (เช่น โดยการทำให้ callback เป็น class method หรือใช้useCallbackใน functional components)
class CallbackRefDetailedExample extends React.Component {
constructor(props) {
super(props);
this.inputElement = null;
}
// เมธอดนี้จะถูกเรียกโดย React เพื่อตั้งค่า ref
setInputElementRef = element => {
if (element) {
console.log('Ref element คือ:', element);
}
this.inputElement = element; // เก็บ DOM element จริง
};
componentDidMount() {
if (this.inputElement) {
this.inputElement.focus();
}
}
render() {
return (
<div>
<label>Callback Ref Input:</label>
<input type="text" ref={this.setInputElementRef} />
</div>
);
}
}
3. `useRef` Hook (Functional Components - สมัยใหม่)
-
กลไก: React Hook ที่ส่งคืน ref object ที่เปลี่ยนแปลงค่าได้ (
{ current: initialValue }) object ที่ส่งคืนจะคงอยู่ตลอดอายุการใช้งานของ functional component - การใช้งานหลัก: เฉพาะภายใน functional components เท่านั้น
-
การกำหนดค่า Ref: คล้ายกับ
createRef, React จะกำหนด DOM element หรือ component instance (หากถูกส่งต่อ) ให้กับ property.currentหลังจากการ mounting และตั้งค่าเป็นnullเมื่อ unmount ค่า.currentยังสามารถอัปเดตด้วยตนเองได้ - เหมาะที่สุดสำหรับ: การจัดการ ref ทั้งหมดใน functional components ยังมีประโยชน์สำหรับการเก็บค่าที่เปลี่ยนแปลงได้ใดๆ ที่ต้องคงอยู่ข้ามการ renders โดยไม่กระตุ้นการ re-render (เช่น ID ของ timer, ค่าก่อนหน้า)
- ข้อดี: ง่าย, เป็นแบบฉบับสำหรับ Hooks ref object คงอยู่ข้ามการ renders หลีกเลี่ยงปัญหาการสร้างซ้ำ สามารถเก็บค่าที่เปลี่ยนแปลงได้ใดๆ ไม่ใช่แค่ DOM nodes
-
ข้อเสีย: ทำงานได้เฉพาะภายใน functional components เท่านั้น ต้องการ
useEffectอย่างชัดเจนสำหรับการโต้ตอบกับ ref ที่เกี่ยวข้องกับวงจรชีวิต (เช่นการโฟกัสเมื่อ mount)
โดยสรุป:
-
หากคุณกำลังเขียน class component และต้องการ ref,
React.createRef()เป็นตัวเลือกที่แนะนำและชัดเจนที่สุด -
หากคุณกำลังเขียน functional component และต้องการ ref,
useRefHook เป็นโซลูชันที่ทันสมัยและเป็นแบบฉบับ - Callback refs ยังคงใช้ได้ แต่โดยทั่วไปจะยืดยาวกว่าและมีแนวโน้มที่จะเกิดปัญหาเล็กน้อยหากไม่ได้นำไปใช้อย่างระมัดระวัง มีประโยชน์สำหรับสถานการณ์ขั้นสูงหรือเมื่อทำงานกับโค้ดเบสรุ่นเก่าหรือบริบทที่ hooks ไม่พร้อมใช้งาน
-
สำหรับการส่ง refs ผ่าน components (โดยเฉพาะ functional components),
React.forwardRef()เป็นสิ่งจำเป็น ซึ่งมักใช้ร่วมกับcreateRefหรือuseRefใน parent component
ข้อพิจารณาระดับโลกและการเข้าถึงขั้นสูงด้วย Refs
ในขณะที่มักจะถูกกล่าวถึงในสุญญากาศทางเทคนิค การใช้ refs ในบริบทของแอปพลิเคชันที่คำนึงถึงระดับโลกมีความหมายที่สำคัญ โดยเฉพาะอย่างยิ่งเกี่ยวกับประสิทธิภาพและการเข้าถึงสำหรับผู้ใช้ที่หลากหลาย
1. การเพิ่มประสิทธิภาพสำหรับอุปกรณ์และเครือข่ายที่หลากหลาย
ผลกระทบของ createRef เองต่อขนาด bundle นั้นน้อยมาก เนื่องจากเป็นส่วนเล็กๆ ของแกน React อย่างไรก็ตาม การดำเนินการที่คุณทำกับ property current อาจมีผลกระทบด้านประสิทธิภาพอย่างมีนัยสำคัญ สำหรับผู้ใช้บนอุปกรณ์ระดับล่างหรือการเชื่อมต่อเครือข่ายที่ช้า (ซึ่งพบบ่อยในหลายส่วนของโลก) การจัดการ DOM ที่ไม่มีประสิทธิภาพอาจนำไปสู่การกระตุก, UI ที่ไม่ตอบสนอง, และประสบการณ์ผู้ใช้ที่ไม่ดี เมื่อใช้ refs สำหรับงานต่างๆ เช่น แอนิเมชัน, การคำนวณเค้าโครงที่ซับซ้อน, หรือการผสานรวมกับไลบรารีของบุคคลที่สามที่มีขนาดใหญ่:
-
Debounce/Throttle Events: หากคุณกำลังใช้ refs เพื่อวัดขนาดในเหตุการณ์
window.resizeหรือscrollให้แน่ใจว่า handlers เหล่านี้ถูก debounce หรือ throttled เพื่อป้องกันการเรียกฟังก์ชันและการอ่าน DOM ที่มากเกินไป -
Batch DOM Reads/Writes: หลีกเลี่ยงการสลับการดำเนินการอ่าน DOM (เช่น
getBoundingClientRect()) กับการดำเนินการเขียน DOM (เช่น การตั้งค่าสไตล์) สิ่งนี้อาจทำให้เกิด layout thrashing เครื่องมืออย่างfastdomสามารถช่วยจัดการสิ่งนี้ได้อย่างมีประสิทธิภาพ -
เลื่อนการดำเนินการที่ไม่สำคัญ: ใช้
requestAnimationFrameสำหรับแอนิเมชัน และsetTimeout(..., 0)หรือrequestIdleCallbackสำหรับการจัดการ DOM ที่มีความสำคัญน้อยกว่า เพื่อให้แน่ใจว่าพวกมันไม่บล็อกเธรดหลักและส่งผลกระทบต่อการตอบสนอง - เลือกอย่างชาญฉลาด: บางครั้งประสิทธิภาพของไลบรารีของบุคคลที่สามอาจเป็นคอขวด ประเมินทางเลือกอื่นหรือพิจารณาการ lazy-loading components ดังกล่าวสำหรับผู้ใช้ที่มีการเชื่อมต่อที่ช้ากว่า เพื่อให้แน่ใจว่าประสบการณ์พื้นฐานยังคงมีประสิทธิภาพทั่วโลก
2. การเพิ่มประสิทธิภาพการเข้าถึง (ARIA Attributes และการนำทางด้วยคีย์บอร์ด)
Refs เป็นเครื่องมือสำคัญในการสร้างเว็บแอปพลิเคชันที่เข้าถึงได้สูง โดยเฉพาะอย่างยิ่งเมื่อสร้าง components UI ที่กำหนดเองที่ไม่มีในเบราว์เซอร์หรือเมื่อต้องการแทนที่พฤติกรรมเริ่มต้น สำหรับผู้ชมทั่วโลก การปฏิบัติตาม Web Content Accessibility Guidelines (WCAG) ไม่ใช่แค่แนวปฏิบัติที่ดี แต่บ่อยครั้งเป็นข้อกำหนดทางกฎหมาย Refs ช่วยให้:
- การจัดการโฟกัสโดยโปรแกรม: ดังที่เห็นกับช่อง input, refs ช่วยให้คุณสามารถตั้งค่าโฟกัส ซึ่งมีความสำคัญสำหรับผู้ใช้คีย์บอร์ดและการนำทางด้วยโปรแกรมอ่านหน้าจอ ซึ่งรวมถึงการจัดการโฟกัสภายใน modals, เมนู dropdown, หรือวิดเจ็ตแบบโต้ตอบ
-
ARIA Attributes แบบไดนามิก: คุณสามารถใช้ refs เพื่อเพิ่มหรืออัปเดต ARIA (Accessible Rich Internet Applications) attributes แบบไดนามิก (เช่น
aria-expanded,aria-controls,aria-live) บน DOM elements สิ่งนี้ให้ข้อมูลเชิงความหมายแก่เทคโนโลยีช่วยเหลือที่อาจไม่สามารถอนุมานได้จาก UI ที่มองเห็นเพียงอย่างเดียวclass CollapsibleSection extends React.Component {
constructor(props) {
super(props);
this.buttonRef = React.createRef();
this.state = { isExpanded: false };
}
toggleExpanded = () => {
this.setState(prevState => ({ isExpanded: !prevState.isExpanded }), () => {
if (this.buttonRef.current) {
// อัปเดต ARIA attribute แบบไดนามิกตาม state
this.buttonRef.current.setAttribute('aria-expanded', this.state.isExpanded);
}
});
};
componentDidMount() {
if (this.buttonRef.current) {
this.buttonRef.current.setAttribute('aria-controls', `section-${this.props.id}`);
this.buttonRef.current.setAttribute('aria-expanded', this.state.isExpanded);
}
}
render() {
const { id, title, children } = this.props;
const { isExpanded } = this.state;
return (
<div style={{ margin: '20px auto', maxWidth: '600px', border: '1px solid #0056b3', borderRadius: '8px', background: '#e7f0fa', overflow: 'hidden' }}>
<h4>
<button
ref={this.buttonRef} // Ref ไปยังปุ่มสำหรับ ARIA attributes
onClick={this.toggleExpanded}
style={{ background: 'none', border: 'none', padding: '15px 20px', width: '100%', textAlign: 'left', cursor: 'pointer', fontSize: '1.2em', color: '#0056b3', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}
id={`section-header-${id}`}
>
{title} <span>▼</span>
</button>
</h4>
{isExpanded && (
<div id={`section-${id}`} role="region" aria-labelledby={`section-header-${id}`} style={{ padding: '0 20px 20px', borderTop: '1px solid #a7d9f7' }}>
{children}
</div>
)}
</div>
);
}
} - การควบคุมการโต้ตอบด้วยคีย์บอร์ด: สำหรับ dropdowns, sliders, หรือ elements แบบโต้ตอบอื่นๆ ที่กำหนดเอง คุณอาจต้องนำ keyboard event handlers ที่เฉพาะเจาะจงไปใช้ (เช่น ปุ่มลูกศรสำหรับการนำทางภายในรายการ) Refs ให้การเข้าถึง DOM element เป้าหมายที่สามารถแนบและจัดการ event listeners เหล่านี้ได้
โดยการใช้ refs อย่างรอบคอบ นักพัฒนาสามารถมั่นใจได้ว่าแอปพลิเคชันของพวกเขาสามารถใช้งานได้และครอบคลุมสำหรับผู้คนที่มีความพิการทั่วโลก ซึ่งเป็นการขยายการเข้าถึงและผลกระทบในระดับโลกอย่างมาก
3. การทำให้เป็นสากล (I18n) และการโต้ตอบที่ปรับตามท้องถิ่น
เมื่อทำงานกับการทำให้เป็นสากล (i18n), refs สามารถมีบทบาทที่ละเอียดอ่อนแต่สำคัญ ตัวอย่างเช่น ในภาษาที่ใช้สคริปต์จากขวาไปซ้าย (RTL) (เช่น ภาษาอาหรับ, ฮีบรู, หรือเปอร์เซีย) ลำดับแท็บธรรมชาติและทิศทางการเลื่อนอาจแตกต่างจากภาษาจากซ้ายไปขวา (LTR) หากคุณกำลังจัดการโฟกัสหรือการเลื่อนโดยโปรแกรมโดยใช้ refs สิ่งสำคัญคือต้องแน่ใจว่าตรรกะของคุณเคารพทิศทางของข้อความของเอกสารหรือ element (attribute dir)
- การจัดการโฟกัสที่รองรับ RTL: ในขณะที่เบราว์เซอร์โดยทั่วไปจัดการลำดับแท็บเริ่มต้นสำหรับ RTL ได้อย่างถูกต้อง หากคุณกำลังนำ focus traps หรือการโฟกัสตามลำดับที่กำหนดเองไปใช้ ให้ทดสอบตรรกะที่ใช้ ref ของคุณอย่างละเอียดในสภาพแวดล้อม RTL เพื่อให้แน่ใจว่าได้รับประสบการณ์ที่สอดคล้องและใช้งานง่าย
-
การวัดเค้าโครงใน RTL: เมื่อใช้
getBoundingClientRect()ผ่าน ref โปรดทราบว่า propertiesleftและrightนั้นสัมพันธ์กับ viewport สำหรับการคำนวณเค้าโครงที่ขึ้นอยู่กับการเริ่มต้น/สิ้นสุดทางภาพ ให้พิจารณาdocument.dirหรือสไตล์ที่คำนวณได้ของ element เพื่อปรับตรรกะของคุณสำหรับเค้าโครง RTL - การผสานรวมไลบรารีของบุคคลที่สาม: ตรวจสอบให้แน่ใจว่าไลบรารีของบุคคลที่สามใดๆ ที่ผสานรวมผ่าน refs (เช่น ไลบรารีการสร้างแผนภูมิ) นั้นรองรับ i18n และจัดการเค้าโครง RTL ได้อย่างถูกต้องหากแอปพลิเคชันของคุณรองรับ ความรับผิดชอบในการตรวจสอบสิ่งนี้มักจะตกอยู่กับนักพัฒนาที่ผสานรวมไลบรารีเข้ากับ React component
สรุป: เชี่ยวชาญการควบคุมแบบ Imperative ด้วย `createRef` สำหรับแอปพลิเคชันระดับโลก
React.createRef() เป็นมากกว่า "ช่องทางหลีกหนี" ใน React; มันเป็นเครื่องมือสำคัญที่เชื่อมช่องว่างระหว่างกระบวนทัศน์ declarative ที่ทรงพลังของ React และความเป็นจริงแบบ imperative ของการโต้ตอบกับ DOM ของเบราว์เซอร์ ในขณะที่บทบาทของมันใน functional components รุ่นใหม่ส่วนใหญ่ถูกแทนที่โดย useRef hook, createRef ยังคงเป็นมาตรฐานและเป็นวิธีที่เหมาะสมที่สุดในการจัดการ refs ภายใน class components ซึ่งยังคงเป็นส่วนสำคัญของแอปพลิเคชันระดับองค์กรจำนวนมากทั่วโลก
โดยการทำความเข้าใจอย่างถ่องแท้เกี่ยวกับการสร้าง, การแนบ, และบทบาทที่สำคัญของ property .current, นักพัฒนาสามารถรับมือกับความท้าทายต่างๆ ได้อย่างมั่นใจ เช่น การจัดการโฟกัสโดยโปรแกรม, การควบคุมสื่อโดยตรง, การผสานรวมอย่างราบรื่นกับไลบรารีของบุคคลที่สามที่หลากหลาย (จากแผนภูมิ D3.js ไปจนถึงตัวแก้ไข rich text ที่กำหนดเอง), และการวัดขนาด element ที่แม่นยำ ความสามารถเหล่านี้ไม่ใช่แค่ความสำเร็จทางเทคนิค แต่เป็นพื้นฐานสำหรับการสร้างแอปพลิเคชันที่มีประสิทธิภาพ, เข้าถึงได้, และเป็นมิตรกับผู้ใช้ในกลุ่มผู้ใช้, อุปกรณ์, และบริบททางวัฒนธรรมที่หลากหลายทั่วโลก
จงจำไว้ว่าให้ใช้พลังนี้อย่างรอบคอบ ให้ความสำคัญกับระบบ state และ prop แบบ declarative ของ React ก่อนเสมอ เมื่อจำเป็นต้องมีการควบคุมแบบ imperative จริงๆ createRef (สำหรับ class components) หรือ useRef (สำหรับ functional components) เสนอกลไกที่แข็งแกร่งและกำหนดไว้อย่างดีเพื่อให้บรรลุเป้าหมาย การเชี่ยวชาญ refs ช่วยให้คุณสามารถจัดการกับกรณีพิเศษและความซับซ้อนของการพัฒนาเว็บสมัยใหม่ ทำให้มั่นใจได้ว่าแอปพลิเคชัน React ของคุณสามารถมอบประสบการณ์ผู้ใช้ที่ยอดเยี่ยมได้ทุกที่ในโลก ในขณะที่ยังคงรักษาประโยชน์หลักของสถาปัตยกรรมที่อิงกับ component อันสง่างามของ React
การเรียนรู้และสำรวจเพิ่มเติม
- เอกสารอย่างเป็นทางการของ React เกี่ยวกับ Refs: สำหรับข้อมูลล่าสุดโดยตรงจากแหล่งที่มา โปรดดู <em>https://react.dev/learn/manipulating-the-dom-with-refs</em>
- ทำความเข้าใจ `useRef` Hook ของ React: เพื่อเจาะลึกถึงสิ่งที่เทียบเท่าใน functional component สำรวจ <em>https://react.dev/reference/react/useRef</em>
- Ref Forwarding ด้วย `forwardRef`: เรียนรู้วิธีส่ง refs ผ่าน components อย่างมีประสิทธิภาพ: <em>https://react.dev/reference/react/forwardRef</em>
- Web Content Accessibility Guidelines (WCAG): สิ่งจำเป็นสำหรับการพัฒนาเว็บระดับโลก: <em>https://www.w3.org/WAI/WCAG22/quickref/</em>
- การเพิ่มประสิทธิภาพ React: แนวทางปฏิบัติที่ดีที่สุดสำหรับแอปที่มีประสิทธิภาพสูง: <em>https://react.dev/learn/optimizing-performance</em>